#include "Uploader.h"
#include <assert.h>
#include <atltrace.h>
#include "MainWindow.h"
#include "Setting.h"

// CUploader* CUploader::m_instance = NULL;

CUploader::CUploader(void)
	: m_countThreadWillUse(5)
	, m_countThreadWorking(0)
	, m_userCancel(false)
	, m_exiting (false)
	, m_pMainWindow (NULL)
{
	m_semaphore = ::CreateSemaphore(NULL, 0, MAX_THREAD_COUNT, _T("xl::ipc::semaphore"));
	assert (m_semaphore != NULL);
	::InitializeCriticalSection(&m_cs);

	// set proxy
	CSetting *pSetting = CSetting::getInstance();
	if (pSetting->isKeyExists("proxy")) {
		std::string proxy = pSetting->getValue("proxy");
		if (proxy.find(':') != std::string::npos) {
			http_set_proxy(proxy);
		}
	}

	// set thread using
	if (pSetting->isKeyExists("thread using")) {
		m_countThreadWillUse = pSetting->getInt("thread using");
		if (m_countThreadWillUse < 0 || m_countThreadWillUse > MAX_THREAD_COUNT) {
			m_countThreadWillUse = 5;
		}
	}

	// start threads
	for (int i = 0; i < MAX_THREAD_COUNT; ++ i) {
		m_threadHandles[i] = (HANDLE)_beginthreadex(NULL, 0, _UploadThread, this, 0, NULL);
	}
}

CUploader::~CUploader(void) {
	m_exiting = true;
	m_userCancel = true;
	ReleaseSemaphore(m_semaphore, MAX_THREAD_COUNT, NULL);
	if (::WaitForMultipleObjects(MAX_THREAD_COUNT, m_threadHandles, TRUE, 1500) == WAIT_TIMEOUT) {
		// TODO ?
	}
	CloseHandle(m_semaphore);

	::DeleteCriticalSection(&m_cs);
}


//////////////////////////////////////////////////////////////////////////
// private
struct _ProgressParam {
	CUploader      *pThis;
	FileInfo::ID    id;
};

unsigned __stdcall CUploader::_UploadThread(void *param) {
	CUploader *pThis = (CUploader *)param;
	assert (pThis != NULL);
	CMainWindow *pMainWindow = CMainWindow::getInstance();
	assert (pMainWindow != NULL);

	for (;;) {
		::WaitForSingleObject(pThis->m_semaphore, INFINITE);
		if (pThis->m_exiting) {
			break;
		}
		::EnterCriticalSection(&pThis->m_cs);
		pThis->m_countThreadWorking ++;
		::LeaveCriticalSection(&pThis->m_cs);

		int connect_failed_times = 0;

		CxDocument *pDoc = pMainWindow->getDocument();
		assert (pDoc);
		ISite *pSite = pMainWindow->getSelectedSite();
		assert (pSite);

		int success_count = 0;
		size_t index = 0;
		FileInfo::ID id;
		while ((id = pDoc->getUpdateId()) != FileInfo::INVALID_ID) {
			CxDocument::INDEX index = pDoc->getIndexById(id);
			ATLASSERT (index != CxDocument::INVALID_INDEX);

			const FileInfo &fi = pDoc->getFileByIndex(index);
			ATLASSERT (fi.status == FileInfo::US_UPLOADING && fi.progress == 0);

			_ProgressParam pp;
			pp.pThis = pThis;
			pp.id = id;

			std::string result;
			HttpCode code = pSite->uploadFile(result, fi.pathname.c_str(), pThis->_ProgressCallback, &pp);
			if (code == CURLE_OK) {
				pDoc->setFileUploadResult(id, FileInfo::US_OK, result);
				success_count ++;
				if (pMainWindow->IsWindow()) {
					pMainWindow->PostMessage(WM_APP_UPLOAD_FILE_SUCCESS, (WPARAM)id, 0);
				}
			} else {
				if (code == CURLE_ABORTED_BY_CALLBACK) {
					// user canceled
					ATLTRACE(_T("Stop at %d\n"), GetTickCount());
					pDoc->setFileUploadResult(id, FileInfo::US_TODO, result);
				} else if (code == CURLE_COULDNT_CONNECT) {
					pDoc->setFileUploadResult(id, FileInfo::US_FAILED, "<html><body>connect server failed</body></html>");
				} else {
					pDoc->setFileUploadResult(id, FileInfo::US_FAILED, result);
				}
			}

			if (code == CURLE_ABORTED_BY_CALLBACK) {
				break;
			} else if (code == CURLE_COULDNT_CONNECT) {
				ATLTRACE(_T("Could't not connect to server\n"));
				connect_failed_times ++;
				if (connect_failed_times > 10) {
					break;
				}
			}
		}

		::EnterCriticalSection(&pThis->m_cs);
		pThis->m_countThreadWorking --;
		::LeaveCriticalSection(&pThis->m_cs);

		if (pMainWindow->IsWindow()) {
			pMainWindow->PostMessage(WM_APP_UPLOAD_THREAD_FINISHED);
		}
	}

	ATLTRACE(_T("Thread exist normally...\n"));
	return 0;
}

int CUploader::_ProgressCallback(
                                 void *clientp,
                                 double dltotal,
                                 double dlnow,
                                 double ultotal,
                                 double ulnow
                                ) {
	_ProgressParam *pParam = (_ProgressParam *)clientp;
	ATLASSERT (pParam);
	CUploader *pThis = pParam->pThis;
	ATLASSERT (pThis != NULL);
	CMainWindow *pMainWindow = CMainWindow::getInstance();
	ATLASSERT (pMainWindow != NULL);

	FileInfo::ID id = pParam->id;
	CxDocument *pDoc = pMainWindow->getDocument();
	CxDocument::INDEX index = pDoc->getIndexById(id);
	ATLASSERT (index != CxDocument::INVALID_INDEX);
	const FileInfo &fi = pDoc->getFileByIndex(index);

	size_t progress = (size_t)((ulnow * 100) / ultotal);
	if (progress == 100) {
		progress = 99; // left time for getting & parsing the result
	}
	pDoc->setFileUploadProgress(id, progress);

	if (pThis->m_userCancel) {
		return 1;
	} else {
		return 0;
	}
}



//////////////////////////////////////////////////////////////////////////
// public 

CUploader* CUploader::getInstance() {
	static CUploader uploader;
	return &uploader;
}

bool CUploader::startUpload() {
	m_userCancel = false;
	ReleaseSemaphore(m_semaphore, m_countThreadWillUse, NULL);
	return true;
}

bool CUploader::stopUpload() {
	m_userCancel = true;
	return true;
}

void CUploader::resetUserCancel() {
	m_userCancel = false;
}